/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain (a) copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sentry.ohos.core;

import ohos.app.Context;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.Resource;
import ohos.utils.BasePacMap;
import ohos.utils.PacMap;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.util.Locale;
import java.util.Properties;

import io.sentry.ILogger;
import io.sentry.SentryLevel;
import io.sentry.util.Objects;

/**
 * Class responsible for reading values from manifest and setting them to the options
 *
 * @since 2021-06-01
 */
public final class ManifestMetadataReader {
    /**
     * DSN
     */
    public static final String DSN = "io.sentry.dsn";
    /**
     * DEBUG
     */
    public static final String DEBUG = "io.sentry.debug";
    /**
     * DEBUG_LEVEL
     */
    public static final String DEBUG_LEVEL = "io.sentry.debug.level";
    /**
     * SAMPLE_RATE
     */
    public static final String SAMPLE_RATE = "io.sentry.sample-rate";
    /**
     * ANR_ENABLE
     */
    public static final String ANR_ENABLE = "io.sentry.anr.enable";
    /**
     * ANR_REPORT_DEBUG
     */
    public static final String ANR_REPORT_DEBUG = "io.sentry.anr.report-debug";
    /**
     * ANR_TIMEOUT_INTERVAL_MILLIS
     */
    public static final String ANR_TIMEOUT_INTERVAL_MILLIS = "io.sentry.anr.timeout-interval-millis";
    /**
     * AUTO_INIT
     */
    public static final String AUTO_INIT = "io.sentry.auto-init";
    /**
     * NDK_ENABLE
     */
    public static final String NDK_ENABLE = "io.sentry.ndk.enable";
    /**
     * NDK_SCOPE_SYNC_ENABLE
     */
    public static final String NDK_SCOPE_SYNC_ENABLE = "io.sentry.ndk.scope-sync.enable";
    /**
     * RELEASE
     */
    public static final String RELEASE = "io.sentry.release";
    /**
     * ENVIRONMENT
     */
    public static final String ENVIRONMENT = "io.sentry.environment";
    /**
     * SESSION_TRACKING_ENABLE
     */
    public static final String SESSION_TRACKING_ENABLE = "io.sentry.session-tracking.enable";
    /**
     * AUTO_SESSION_TRACKING_ENABLE
     */
    public static final String AUTO_SESSION_TRACKING_ENABLE = "io.sentry.auto-session-tracking.enable";
    /**
     * SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS
     */
    public static final String SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS =
        "io.sentry.session-tracking.timeout-interval-millis";

    /**
     * BREADCRUMBS_ABILITY_LIFECYCLE_ENABLE
     */
    public static final String BREADCRUMBS_ABILITY_LIFECYCLE_ENABLE =
        "io.sentry.breadcrumbs.ability-lifecycle";
    /**
     * BREADCRUMBS_APP_LIFECYCLE_ENABLE
     */
    public static final String BREADCRUMBS_APP_LIFECYCLE_ENABLE = "io.sentry.breadcrumbs.app-lifecycle";
    /**
     * BREADCRUMBS_SYSTEM_EVENTS_ENABLE
     */
    public static final String BREADCRUMBS_SYSTEM_EVENTS_ENABLE = "io.sentry.breadcrumbs.system-events";
    /**
     * BREADCRUMBS_APP_COMPONENTS_ENABLE
     */
    public static final String BREADCRUMBS_APP_COMPONENTS_ENABLE = "io.sentry.breadcrumbs.app-components";

    /**
     * UNCAUGHT_EXCEPTION_HANDLER_ENABLE
     */
    public static final String UNCAUGHT_EXCEPTION_HANDLER_ENABLE =
        "io.sentry.uncaught-exception-handler.enable";
    /**
     * TRACES_SAMPLE_RATE
     */
    public static final String TRACES_SAMPLE_RATE = "io.sentry.traces.sample-rate";
    /**
     * TRACES_ABILITY_ENABLE
     */
    public static final String TRACES_ABILITY_ENABLE = "io.sentry.traces.ability.enable";
    /**
     * TRACES_ABILITY_AUTO_FINISH_ENABLE
     */
    public static final String TRACES_ABILITY_AUTO_FINISH_ENABLE =
        "io.sentry.traces.ability.auto-finish.enable";
    /**
     * ATTACH_THREADS
     */
    public static final String ATTACH_THREADS = "io.sentry.attach-threads";

    /**
     * ManifestMetadataReader ctor
     */
    private ManifestMetadataReader() {
    }

    /**
     * Reads configurations from Manifest and sets it to the options
     *
     * @param context the application context
     * @param options the SentryOhosOptions
     */
    public static void applyMetadata(
        final @NotNull Context context, final @NotNull SentryOhosOptions options) {
        Objects.requireNonNull(context, "The application context is required.");
        Objects.requireNonNull(options, "The options object is required.");

        try {
            BasePacMap metadata = getMetadata(context);

            final ILogger logger = options.getLogger();

            if (metadata != null) {
                options.setDebug(readBool(metadata, logger, DEBUG, options.isDebug()));

                if (options.isDebug()) {
                    final String level =
                        readString(
                            metadata,
                            logger,
                            DEBUG_LEVEL,
                            options.getDiagnosticLevel().name().toLowerCase(Locale.ROOT));
                    if (level != null) {
                        options.setDiagnosticLevel(SentryLevel.valueOf(level.toUpperCase(Locale.ROOT)));
                    }
                }

                options.setAnrEnabled(readBool(metadata, logger, ANR_ENABLE, options.isAnrEnabled()));

                // deprecated
                final boolean isEnableSessionTracking =
                    readBool(
                        metadata, logger, SESSION_TRACKING_ENABLE, options.isEnableAutoSessionTracking());

                // use enableAutoSessionTracking as fallback
                options.setEnableAutoSessionTracking(
                    readBool(metadata, logger, AUTO_SESSION_TRACKING_ENABLE, isEnableSessionTracking));

                if (options.getSampleRate() == null) {
                    final Double sampleRate = readDouble(metadata, logger, SAMPLE_RATE);
                    if (sampleRate != -1) {
                        options.setSampleRate(sampleRate);
                    }
                }

                options.setAnrReportInDebug(
                    readBool(metadata, logger, ANR_REPORT_DEBUG, options.isAnrReportInDebug()));

                options.setAnrTimeoutIntervalMillis(
                    readLong(
                        metadata,
                        logger,
                        ANR_TIMEOUT_INTERVAL_MILLIS,
                        options.getAnrTimeoutIntervalMillis()));

                final String dsn = readString(metadata, logger, DSN, options.getDsn());
                if (dsn == null) {
                    options
                        .getLogger()
                        .log(SentryLevel.FATAL, "DSN is required. Use empty string to disable SDK.");
                } else if (dsn.isEmpty()) {
                    options.getLogger().log(SentryLevel.DEBUG, "DSN is empty, disabling sentry-ohos");
                }
                options.setDsn(dsn);

                options.setEnableNdk(readBool(metadata, logger, NDK_ENABLE, options.isEnableNdk()));

                options.setEnableScopeSync(
                    readBool(metadata, logger, NDK_SCOPE_SYNC_ENABLE, options.isEnableScopeSync()));

                options.setRelease(readString(metadata, logger, RELEASE, options.getRelease()));

                options.setEnvironment(readString(metadata, logger, ENVIRONMENT, options.getEnvironment()));

                options.setSessionTrackingIntervalMillis(
                    readLong(
                        metadata,
                        logger,
                        SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS,
                        options.getSessionTrackingIntervalMillis()));

                options.setEnableAbilityLifecycleBreadcrumbs(
                    readBool(
                        metadata,
                        logger,
                        BREADCRUMBS_ABILITY_LIFECYCLE_ENABLE,
                        options.isEnableAbilityLifecycleBreadcrumbs()));

                options.setEnableAppLifecycleBreadcrumbs(
                    readBool(
                        metadata,
                        logger,
                        BREADCRUMBS_APP_LIFECYCLE_ENABLE,
                        options.isEnableAppComponentBreadcrumbs()));

                options.setEnableSystemEventBreadcrumbs(
                    readBool(
                        metadata,
                        logger,
                        BREADCRUMBS_SYSTEM_EVENTS_ENABLE,
                        options.isEnableSystemEventBreadcrumbs()));

                options.setEnableAppComponentBreadcrumbs(
                    readBool(
                        metadata,
                        logger,
                        BREADCRUMBS_APP_COMPONENTS_ENABLE,
                        options.isEnableAppComponentBreadcrumbs()));

                options.setEnableUncaughtExceptionHandler(
                    readBool(
                        metadata,
                        logger,
                        UNCAUGHT_EXCEPTION_HANDLER_ENABLE,
                        options.isEnableUncaughtExceptionHandler()));

                options.setAttachThreads(
                    readBool(metadata, logger, ATTACH_THREADS, options.isAttachThreads()));

                if (options.getTracesSampleRate() == null) {
                    final Double tracesSampleRate = readDouble(metadata, logger, TRACES_SAMPLE_RATE);
                    if (tracesSampleRate != -1) {
                        options.setTracesSampleRate(tracesSampleRate);
                    }
                }

                options.setEnableAutoAbilityLifecycleTracing(
                    readBool(
                        metadata,
                        logger,
                        TRACES_ABILITY_ENABLE,
                        options.isEnableAutoAbilityLifecycleTracing()));

                options.setEnableAbilityLifecycleTracingAutoFinish(
                    readBool(
                        metadata,
                        logger,
                        TRACES_ABILITY_AUTO_FINISH_ENABLE,
                        options.isEnableAbilityLifecycleTracingAutoFinish()));
            }
            options
                .getLogger()
                .log(SentryLevel.INFO, "Retrieving configuration from json");
        } catch (Exception e) {
            options
                .getLogger()
                .log(
                    SentryLevel.ERROR, "Failed to read configuration from json.", e);
        }
    }

    private static boolean readBool(
        final @NotNull BasePacMap metadata,
        final @NotNull ILogger logger,
        final @NotNull String key,
        final boolean isDefaultValue) {
        final boolean value = metadata.getBooleanValue(key, isDefaultValue);
        logger.log(SentryLevel.DEBUG, "%s read: %s", key, value);
        return value;
    }

    private static @Nullable String readString(
        final @NotNull BasePacMap metadata,
        final @NotNull ILogger logger,
        final @NotNull String key,
        final @Nullable String defaultValue) {
        final String value = metadata.getString(key, defaultValue);
        logger.log(SentryLevel.DEBUG, "%s read: %s", key, value);
        return value;
    }

    private static @NotNull Double readDouble(
        final @NotNull BasePacMap metadata, final @NotNull ILogger logger, final @NotNull String key) {
        // manifest meta-data only reads float
        final Double value = ((Float) metadata.getFloatValue(key, -1)).doubleValue();
        logger.log(SentryLevel.DEBUG, "%s read: %s", key, value);
        return value;
    }

    private static long readLong(
        final @NotNull BasePacMap metadata,
        final @NotNull ILogger logger,
        final @NotNull String key,
        final long defaultValue) {
        // manifest meta-data only reads int if the value is not big enough
        final long value = metadata.getIntValue(key, (int) defaultValue);
        logger.log(SentryLevel.DEBUG, "%s read: %s", key, value);
        return value;
    }

    /**
     * Checks if auto init is enabled or disabled
     *
     * @param context the application context
     * @param logger the Logger interface
     * @return true if auto init is enabled or false otherwise
     */
    public static boolean isAutoInit(final @NotNull Context context, final @NotNull ILogger logger) {
        Objects.requireNonNull(context, "The application context is required.");

        boolean isAutoInit = true;
        try {
            final BasePacMap metadata = getMetadata(context);
            if (metadata != null) {
                isAutoInit = readBool(metadata, logger, AUTO_INIT, true);
            }
            logger.log(SentryLevel.INFO, "Retrieving auto-init from json");
        } catch (Exception e) {
            logger.log(SentryLevel.ERROR, "Failed to read auto-init from json.", e);
        }
        return isAutoInit;
    }

    /**
     * Returns the BasePacMap attached from the given Context
     *
     * @param context the application context
     * @return BasePacMap
     */
    public static @Nullable BasePacMap getMetadata(final @NotNull Context context) {
        RawFileEntry rawFileEntry = context.getResourceManager().getRawFileEntry("resources/rawfile/config.properties");
        Resource resource = null;
        PacMap pacMap = new PacMap();
        try {
            resource = rawFileEntry.openRawFile();
            Properties properties = new Properties();
            properties.load(resource);
            String dsn = properties.getProperty(DSN);
            boolean ndkScopeSyncEnable = Boolean.parseBoolean(properties.getProperty(NDK_SCOPE_SYNC_ENABLE));
            Long trackingTimeoutIntervalMillis =
                Long.parseLong(properties.getProperty(SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS));
            Float tracesSampleRate = Float.parseFloat(properties.getProperty(TRACES_SAMPLE_RATE));
            boolean tracesAbilityAutoFinishEnable =
                Boolean.parseBoolean(properties.getProperty(TRACES_ABILITY_AUTO_FINISH_ENABLE));
            boolean debug = Boolean.parseBoolean(properties.getProperty(DEBUG));
            boolean environment = Boolean.parseBoolean(properties.getProperty(ENVIRONMENT));
            pacMap.putString(DSN, dsn);
            pacMap.putBooleanValue(NDK_SCOPE_SYNC_ENABLE, ndkScopeSyncEnable);
            pacMap.putLongValue(SESSION_TRACKING_TIMEOUT_INTERVAL_MILLIS, trackingTimeoutIntervalMillis);
            pacMap.putFloatValue(TRACES_SAMPLE_RATE, tracesSampleRate);
            pacMap.putBooleanValue(TRACES_ABILITY_AUTO_FINISH_ENABLE, tracesAbilityAutoFinishEnable);
            pacMap.putBooleanValue(DEBUG, debug);
            pacMap.putBooleanValue(ENVIRONMENT, environment);
        } catch (IOException e) {
            e.getMessage();
        } finally {
            if (resource != null) {
                try {
                    resource.close();
                } catch (IOException e) {
                    e.getMessage();
                }
            }
        }
        return pacMap;
    }
}
